package repository

import (
	"JK-Junior-Go-Engineer-Camp/webook/internal/domain"
	"JK-Junior-Go-Engineer-Camp/webook/internal/repository/cache"
	"JK-Junior-Go-Engineer-Camp/webook/internal/repository/dao"
	"context"
	"database/sql"
	"time"
)

var (
	ErrDuplicateUser = dao.ErrDuplicateEmail
	ErrUserNotFound  = dao.ErrRecordNotFound
)

type UserRepository interface {
	Create(ctx context.Context, u domain.User) error
	FindByPhone(ctx context.Context, phone string) (domain.User, error)
	FindByEmail(ctx context.Context, email string) (domain.User, error)
	FindById(ctx context.Context, uid int64) (domain.User, error)
	UpdateById(ctx context.Context, user domain.User) error
	FindByWechat(ctx context.Context, openID string) (domain.User, error)
}

// CachedUserRepository 支持 缓存 的repository实现
type CachedUserRepository struct {
	dao   dao.UserDAO
	cache cache.UserCache
}

func NewCachedUserRepository(dao dao.UserDAO, c cache.UserCache) UserRepository {
	return &CachedUserRepository{
		dao:   dao,
		cache: c,
	}
}

//type DBConfig struct {
//	DSN string
//}
//type CacheConfig struct {
//	Addr string
//}
//
//// NewUserRepositoryV1 非依赖注入的写法
//func NewUserRepositoryV1(dbCfg DBConfig, cacheCfg CacheConfig) (*CachedUserRepository, error) {
//	db, err := gorm.Open(mysql.Open(dbCfg.DSN))
//	if err != nil {
//		return nil, err
//	}
//	userDAO := dao.NewUserDAO(db)
//	userCache := cache.NewRedisUserCache(redis.NewClient(&redis.Options{
//		Addr: cacheCfg.Addr,
//	}))
//
//	return &CachedUserRepository{
//		dao:   userDAO,
//		cache: userCache,
//	}, nil
//}

// Create Repository层面没有注册的概念，而是叫做创建，所以没有继续叫Signup
func (repo *CachedUserRepository) Create(ctx context.Context, u domain.User) error {
	return repo.dao.Insert(ctx, repo.domainToDao(u))
}

// FindByEmail 根据邮箱查找用户
func (repo *CachedUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
	daoUser, err := repo.dao.FindByEmail(ctx, email)
	if err != nil {
		return domain.User{}, err
	}
	return repo.daoToDomain(daoUser), nil
}

// UpdateById 修改个人信息
// 对应Up
func (repo *CachedUserRepository) UpdateById(ctx context.Context, user domain.User) error {
	err := repo.dao.UpdateById(ctx, dao.User{
		Id:       user.Id,
		Nickname: user.Nickname,
		Birthday: user.Birthday.UnixMilli(),
		AboutMe:  user.AboutMe,
	})
	return err
}

// FindById 根据ID查询
func (repo *CachedUserRepository) FindById(ctx context.Context, uid int64) (domain.User, error) {
	// 先查缓存
	domainUser, err := repo.cache.Get(ctx, uid)
	if err == nil {
		return domainUser, nil
	}
	// 如果没缓存，就查数据库，然后加到缓存中
	user, err := repo.dao.FindById(ctx, uid)
	if err != nil {
		return domain.User{}, err
	}
	domainUser = repo.daoToDomain(user)
	err = repo.cache.Set(ctx, domainUser)

	return domainUser, nil
}

// FindByIdV1 Redis里面没有查到数据的时候才去往数据库去查询，如果Redis本身出现问题，不再继续查询数据库。
func (repo *CachedUserRepository) FindByIdV1(ctx context.Context, uid int64) (domain.User, error) {
	// 先查缓存
	domainUser, err := repo.cache.Get(ctx, uid)
	switch err {
	case nil:
		return domainUser, nil
	case cache.ErrKeyNotExist: // key不存在
		user, err := repo.dao.FindById(ctx, uid)
		if err != nil {
			return domain.User{}, err
		}
		domainUser = repo.daoToDomain(user)
		err = repo.cache.Set(ctx, domainUser)
		return domainUser, nil
	default: // 访问 Redis 错误
		// 接近降级的写法
		return domain.User{}, err
	}
}

// FindByPhone 通过手机号查询用户
func (repo *CachedUserRepository) FindByPhone(ctx context.Context, phone string) (domain.User, error) {
	daoUser, err := repo.dao.FindByPhone(ctx, phone)
	if err != nil {
		return domain.User{}, err
	}
	return repo.daoToDomain(daoUser), nil
}

// FindByWechat 通过微信的OpenID查询用户
func (repo *CachedUserRepository) FindByWechat(ctx context.Context, openID string) (domain.User, error) {
	daoUser, err := repo.dao.FindByWechat(ctx, openID)
	if err != nil {
		return domain.User{}, err
	}
	return repo.daoToDomain(daoUser), nil
}

// 我们需要把 daoUser 转换成 domainUser ，
// 因为后面都会用到，所以抽成一个通用的方法
func (repo *CachedUserRepository) daoToDomain(daoUser dao.User) domain.User {
	return domain.User{
		Id:       daoUser.Id,
		Email:    daoUser.Email.String,
		Phone:    daoUser.Phone.String,
		Password: daoUser.Password,
		Nickname: daoUser.Nickname,
		Birthday: time.UnixMilli(daoUser.Birthday),
		AboutMe:  daoUser.AboutMe,
		// 返回与给定Unix时间对应的本地时间
		Ctime: time.UnixMilli(daoUser.Ctime),
		WechatInfo: domain.WechatInfo{
			Openid:  daoUser.WechatOpenId.String,
			UnionId: daoUser.WechatUnionId.String,
		},
	}
}

// domainUser 转换成 daoUser 类型
func (repo *CachedUserRepository) domainToDao(domainUser domain.User) dao.User {
	return dao.User{
		Id: domainUser.Id,
		Email: sql.NullString{
			String: domainUser.Email,
			Valid:  domainUser.Email != "",
		},
		Phone: sql.NullString{
			String: domainUser.Phone,
			Valid:  domainUser.Phone != "",
		},
		Password: domainUser.Password,
		Nickname: domainUser.Nickname,
		Birthday: domainUser.Birthday.UnixMilli(),
		AboutMe:  domainUser.AboutMe,
		// 返回与给定Unix时间对应的本地时间
		Ctime: domainUser.Ctime.UnixMilli(),
		WechatOpenId: sql.NullString{
			String: domainUser.Openid,
			Valid:  domainUser.Openid != "",
		},
		WechatUnionId: sql.NullString{
			String: domainUser.UnionId,
			Valid:  domainUser.UnionId != "",
		},
	}
}
